import asyncio
import aiohttp
import websockets
import requests
import time
import json
import base64
import os
import logging
import sys

sys.path.append("D:/Myprog")
FilePath = "D:/Myprog/DDbot/File"

from Panda.Core.Log import ensure_dir
from Panda.Core.Log import logger
import socket

from Panda.Reply import check_msg
from datetime import datetime

logger.setLevel(logging.DEBUG)

import platform

robotCode = "dingfjytrhvnxlicvkbw"
robotSecret = "5Ie_qScP3oQqN-qqdBI6MY_JS3Dh78N4lHjJH65Wf2bCbpxTXc_bmpcl_GYTWOqA"
ensure_dir(FilePath)

_msg = {
    "specVersion": "1.0",
    "type": "CALLBACK",
    "headers": {
        "appId": "85ec5ee5-35f0-4999-90a7-93f8ebd7c066",
        "connectionId": "b38399a1-f836-11ef-9fef-8a9de39634fb",
        "contentType": "application/json",
        "messageId": "212c67af_1177_19521e8318a_1cd7696",
        "time": "1741010088011",
        "topic": "/v1.0/im/bot/messages/get",
    },
    "data": {
        "senderPlatform": "Android",
        "conversationId": "cidngDK2owx/GYDMFzM/f2oblC/8rFnj8yyIe93TkmEEHc=",
        "chatbotCorpId": "dingc5eb6027b6a53ad14ac5d6980864d335",
        "chatbotUserId": "$:LWCP_v1:$w9OKK3yjTVjl/D7nHw4unCTu6jlc3nAh",
        "msgId": "msg9oHYseQKm3zX30YCE17g2Q==",
        "senderNick": "熊亚飞",
        "isAdmin": True,
        "senderStaffId": "manager3876",
        "sessionWebhookExpiredTime": 1741015826052,
        "createAt": 1741010425760,
        "senderCorpId": "dingc5eb6027b6a53ad14ac5d6980864d335",
        "conversationType": "1",
        "senderId": "$:LWCP_v1:$ur6M5VImxdoXoxrfVT5l5gtVOZlCm/Eh",
        "sessionWebhook": "https://oapi.dingtalk.com/robot/sendBySession?session=7c918caf664d758deb44af528a81c05f",
        "text": {"content": "你好"},
        "robotCode": "dingfjytrhvnxlicvkbw",
        "msgtype": "text",
    },
}

cc = {
    "richText": [
        {"text": "学习图片尝试@@"},
        {
            "pictureDownloadCode": "mTgsM2D8KNoPfgCd0xlbiLXQpNcDZvLxM27MuYxRyhQMNpUTgUWMKGfmnIE2JQTvAhT6C9oLU0hIVR2QyqX+3lKbeVxChkyDdACbGktlLsyUusu5x9krGtthDNgh9yghEDQ2LhH4GhmcWl+8nVNAoa93ffb6VoXxMxWIs/AqgKkvE9xXSw+GCnGDJiznMgHkCdWjakF5ipnHSxprqdI4Vg==",
            "downloadCode": "mIofN681YE3f/+m+Nntqpf9ys9NioZgqzrXhA2107lutG4lYiPfltZFBgLf1uAu3FWDtFxZpsDQ4vb8BjFtHL60gHb86EvjJWnH4wIi2gRGtkSzxa5NFaetR5mv0TKXEb/KuswKu6JT+XIo5f+YNJ5Nrz9nHa/htOiVE4TUtz+rY9pJ6hf0uRqt/1JV9Uy9IuQPO4qGCdZlFGnDKLD4oFAALle1L0PLC/nNOJabmY+A=",
            "type": "picture",
        },
    ]
}


class MessageType:

    (
        sampleText,
        sampleImage,
        sampleMark,
        sampleLink,
        sampleCard,
        sampleAudio,
        sampleFile,
        sampleVideo,
    ) = (
        "sampleText",
        "sampleImageMsg",
        "sampleMarkdown",
        "sampleLink",
        "sampleActionCard",
        "sampleAudio",
        "sampleFile",
        "sampleVideo",
    )

    FILE, TEXT, AUDIO, PICTURE, VIDEO = "file", "text", "audio", "picture", "video"

    @staticmethod
    def text(content):
        return str({"content": content})

    @staticmethod
    def link(text, url):
        return str(
            {
                "text": url,
                "title": text,
                "picUrl": "@lALPDfJ6V_FPDmvNAfTNAfQ",
                "messageUrl": url,
            }
        )

    @staticmethod
    def markdown(title, markdown):
        return str(
            {
                "title": title,
                "text": markdown,
            }
        )

    @staticmethod
    def card(text, url):
        return str(
            {
                "title": "title",
                "text": text,
                "singleTitle": text,
                "singleURL": url,
            }
        )

    @staticmethod
    def image(meida):
        return str({"photoURL": meida})

    @staticmethod
    def voice(filemsg):
        return str({"mediaId": filemsg["meida_id"], "duration": "999"})

    @staticmethod
    def video(filemsg):
        return str(
            {
                "duration": "999",
                "videoMediaId": filemsg["media_id"],
                "videoType": "mp4",
                "picMediaId": filemsg["picture_id"],
            }
        )

    @staticmethod
    def file(filemsg):
        return str(
            {
                "mediaId": filemsg["media_id"],
                "fileName": filemsg["file_name"],
                "fileType": filemsg["file_type"],
            }
        )


class Content:

    def __init__(self, content):
        assert isinstance(content, dict)
        self.text = content.get("content", "")
        self.pictureDownloadCode = content.get("pictureDownloadCode", "")
        self.downloadCode = content.get("downloadCode", "")
        self.richText = content.get("richText", [])
        for rich in self.richText:
            self.text += rich.get("text", "")
            self.downloadCode = rich.get("downloadCode", self.downloadCode)
            self.pictureDownloadCode = rich.get(
                "pictureDownloadCode", self.pictureDownloadCode
            )

    def __repr__(self):
        exclude = ["bot"]
        return str({k: v for k, v in self.__dict__.items() if k not in exclude and v})


class DDMessage:
    def __init__(self, bot, msg):
        assert isinstance(msg, dict)
        self.bot = bot
        # self.type = msg.get("type", "")
        self.data = self.Data(msg.get("data", {}))
        self.headers = self.Headers(msg.get("headers", {}))

        self.text = self.data.content.text
        self.downurl = self.data.content.downloadCode
        # self.webhook = self.data.sessionWebhook
        # self.conversationType = self.data.conversationType
        self.str_time = time.strftime(
            "%Y-%m-%d-%H.%M.%S", time.localtime(self.data.createAt // 1000)
        )

    def __repr__(self):
        exclude = ["bot"]
        return str({k: v for k, v in self.__dict__.items() if k not in exclude and v})

    class Data:

        def __init__(self, data):
            if isinstance(data, str):
                data = json.loads(data)
            self.platform = data.get("senderPlatform", "")
            self.conversationId = data.get("conversationId", "")
            self.chatbotCorpId = data.get("chatbotCorpId", "")
            self.chatbotUserId = data.get("chatbotUserId", "")
            self.msgId = data.get("msgId", "")
            self.senderNick = data.get("senderNick", "")
            self.isAdmin = data.get("isAdmin", False)
            self.senderStaffId = data.get("senderStaffId", "")
            self.sessionTime = data.get("sessionWebhookExpiredTime", 0)
            self.createAt = data.get("createAt", 0)
            self.senderCorpId = data.get("senderCorpId", "")
            self.conversationType = data.get("conversationType", 1)
            self.senderId = data.get("senderId", "")
            self.sessionWebhook = data.get("sessionWebhook", "")
            self.content = Content(data.get("text", {}) or data.get("content", {}))
            self.msgtype = data.get("msgtype", "")

        def __repr__(self):
            exclude = [
                "platform",
                "chatbotUserId",
                "chatbotCorpId",
                "senderCorpId",
                "senderId",
                "sessionWebhook",
            ]
            return str({k: v for k, v in self.__dict__.items() if k not in exclude})

    class Headers:

        def __init__(self, headers):
            self.appId = headers.get("appId", "")
            self.connectionId = headers.get("connectionId", "")
            self.contentType = headers.get("contentType", "")
            self.messageId = headers.get("messageId", "")
            self.time = headers.get("time", "")
            self.topic = headers.get("topic", "")

        def __repr__(self):
            return str({"topic": self.topic})

    @property
    def function(self):
        if self.data.conversationType == "1":
            return "direct_message"
        else:
            return "group_message"

    @property
    def is_expired(self):
        return self.data.createAt // 1000 < int(time.time()) - 10


class Token:
    def __init__(self, client, robotCode, robotSecret):
        self.robotCode = robotCode
        self.robotSecret = robotSecret
        self.client = client
        self.expire_time = 0
        self.token_string = ""

    async def update_token(self):
        if time.time() > self.expire_time:
            self.token_string = await self.get_app_token()

    async def get_app_token(self):
        """获取钉钉内部应用的token"""
        url = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
        data = {"appKey": self.robotCode, "appSecret": self.robotSecret}
        res = await self.client.make_request("POST", url=url, json=data)
        try:
            self.token = res["accessToken"]
            self.expire_time = time.time() + res["expireIn"] - 300
            return self.token
        except Exception as e:
            print(e.args)


class DDBot:
    def __init__(self, robotCode, robotSecret):
        self.robotCode = robotCode
        self.robotSecret = robotSecret

        self.url_base = "https://api.dingtalk.com"
        self.loop = asyncio.new_event_loop()
        self.session = None
        self.token: Token = Token(self, robotCode, robotSecret)
        self.headers = {}

    def run(self):
        asyncio.set_event_loop(self.loop)
        self.loop.run_until_complete(self.start())

    async def get_wss_url(self):
        self.url_conn = f"{self.url_base}/v1.0/gateway/connections/open"
        self.headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": (
                "DingTalkStream/1.0 SDK/0.18.1 Python/%s "
                "(+https://github.com/open-dingtalk/dingtalk-stream-sdk-python)"
            )
            % (platform.python_version()),
        }
        self.topic = [
            # {"type": "EVENT", "topic": "*"},
            {"type": "CALLBACK", "topic": "/v1.0/im/bot/messages/get"},
            {"type": "CALLBACK", "topic": "/v1.0/im/bot/messages/delegate"},
        ]
        self.request_body = {
            "clientId": robotCode,
            "clientSecret": robotSecret,
            "subscriptions": self.topic,
            "ua": "dingtalk-sdk-python/v0.18.1",
            "localIp": self.get_host_ip(),
        }

        res = await self.make_request("POST", self.url_conn, json=self.request_body)

        endpoint = res["endpoint"]
        ticket = res["ticket"]
        self.wss_url = endpoint + "?ticket=" + ticket

    async def make_request(self, method, url, json=None, **kwargs):
        if not self.session or self.session.closed:
            self.session = aiohttp.ClientSession()
        try:
            async with self.session.request(
                method, url, headers=self.headers, json=json, **kwargs
            ) as response:
                res = await response.json()
                logger.debug(
                    f"\n\t{method:<4}: {url}\n\tHEAD: {self.headers}\n\tJSON: {json}\n\tRESP: {res}"
                )
                return res
        except aiohttp.client_exceptions.ClientConnectorError as e:
            self.session.close()
            logger.error(e.args)

    async def start(self):
        retry_time = 0
        while retry_time < 1000:
            retry_time += 1
            await asyncio.sleep(5)
            try:
                await self.get_wss_url()
                logger.info(f"\nWssurl: {self.wss_url}")
                async with websockets.connect(self.wss_url) as wss:
                    self.wss = wss
                    self.closed = False
                    logger.critical("Connected to gateway.")
                    async for msg in self.wss:
                        # logger.warning(json.loads(msg))
                        res = json.loads(msg)
                        rec = DDMessage(self, res)
                        logger.info(rec)
                        # if rec.is_expired: //服务器时间出错，导致排查不可用
                        #     logger.warning("Msg expired.")
                        #     continue
                        function = getattr(self, rec.function)
                        logger.error(function)
                        self.loop.create_task(function(rec))
            except Exception as e:
                logger.error(e.args)
                await asyncio.sleep(10)

    async def get_down_url(self, msg: DDMessage):

        data = {
            "downloadCode": msg.downurl,
            "robotCode": self.robotCode,
        }
        url = "https://api.dingtalk.com/v1.0/robot/messageFiles/download"
        res = await self.make_request("POST", url, json=data)

        # logger.critical(res["downloadUrl"])
        down_url = res["downloadUrl"]

        res = requests.get(down_url)
        logger.info(down_url)
        ext = down_url.split("?", 1)[0].split(".")[-1]
        logger.info(ext)
        with open(f"{FilePath}/{msg.str_time}.{ext}", "wb") as f:
            f.write(res.content)

        import filetype

        if ext == "file":
            kind = filetype.guess(f"{msg.str_time}.{ext}")
            if kind:
                ext_new = kind.mime.split("/")[-1]
            else:
                ext_new = "jio"
                logger.warning(f"Received file type is : {ext_new}")
            os.rename(f"{msg.str_time}.{ext}", f"{msg.str_time}.{ext_new}")

    async def direct_message(self, msg: DDMessage):
        url = f"{self.url_base}/v1.0/robot/oToMessages/batchSend"
        await self.token.update_token()
        self.headers["x-acs-dingtalk-access-token"] = self.token.token_string

        if msg.downurl:
            await self.get_down_url(msg)

        reply_msg = check_msg(msg, None, True, True)
        if os.path.exists(reply_msg):
            filemsg = await self.get_media_id(reply_msg)
        elif reply_msg:
            filemsg = {}
        else:
            return

        base_data = {
            "robotCode": self.robotCode,
            "userIds": [msg.data.senderStaffId],
        }

        if filemsg.get("upload_type") == "image":
            data = {
                "msgKey": MessageType.sampleImage,
                "msgParam": MessageType.image(filemsg["media_id"]),
            }
        elif filemsg.get("upload_type") == "file":
            data = {
                "msgKey": MessageType.sampleFile,
                "msgParam": MessageType.file(filemsg),
            }
        elif filemsg.get("upload_type") == "voice":
            data = {
                "msgKey": MessageType.sampleVoice,
                "msgParam": MessageType.voice(filemsg),
            }
        elif filemsg.get("upload_type") == "video":
            data = {
                "msgKey": MessageType.sampleVoice,
                "msgParam": MessageType.video(filemsg),
            }
        else:

            if "markdown@" in reply_msg:
                title, markdown = reply_msg.split("markdown@", 1)
                data = {
                    "msgKey": MessageType.sampleMark,
                    "msgParam": MessageType.markdown(title, markdown),
                }
            elif "url@" in reply_msg:
                title, url = reply_msg.split("url@", 1)
                data = {
                    "msgKey": MessageType.sampleLink,
                    "msgParam": MessageType.link(title, url),
                }
            elif "card@" in reply_msg:
                title, card = reply_msg.split("card@", 1)
                data = {
                    "msgKey": MessageType.sampleCard,
                    "msgParam": MessageType.card(title, card),
                }
            else:
                data = {
                    "msgKey": MessageType.sampleText,
                    "msgParam": MessageType.text(reply_msg),
                }
        base_data.update(data)
        await self.make_request("POST", url, json=base_data)
        # raise NotImplementedError

    async def group_message(self, msg):
        NotImplementedError

    def get_host_ip(self):
        """查询本机ip"""
        ip = ""
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
        except Exception:
            ip = "192.168.1.110"
            logger.error(f"Failed to get local ip. Used default ip!")
        finally:
            s.close()
        logger.info(f"Local IP is {ip}")
        return ip

    async def get_media_id(self, path: str) -> dict:
        """获取上传附件的id"""
        if path.endswith((".jpg", ".jpeg", ".gif", ".bmp", ".png")):
            upload_type = "image"
        elif path.endswith((".amr", ".mp3", ".wav")):
            upload_type = "voice"
        elif path.endswith(".mp4"):
            upload_type = "video"
        elif path.endswith(
            (".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".zip", ".pdf", ".rar")
        ):
            upload_type = "file"
        else:
            upload_type = "file"
            logger.error("Unsupported media type")
        url = f"{self.url_base}/media/upload?access_token={self.token.token_string}&type={upload_type}"
        files = {"media": open(path, "rb")}
        response = requests.post(url, files=files)
        if response.status_code != 200:
            logger.error(response.reason)
            return {}
        res = response.json()

        res["file_name"] = os.path.basename(path)
        res["file_type"] = os.path.splitext(path)[1][1:]
        res["upload_type"] = upload_type
        logger.info(res)
        return res


if __name__ == "__main__":
    bot = DDBot(robotCode, robotSecret)
    bot.run()
    # print(bot.get_host_ip())
